package assignment4.part2;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;

import assignment4.part1.Decrypt;
import assignment4.part1.Encrypt;
import assignment4.part1.SymmetricCrypt;

public class A {
	public static String key = "dfgd72-dfg8zaz!";
	
	private final String address;
	private final int port;
	private Socket socket;
	private ObjectInputStream ois;
	private ObjectOutputStream oos;
	
	public A(String address, int port){
		this.address = address;
		this.port = port;
		
		try{
			this.socket = new Socket(this.address, this.port);
			System.out.println("Socket created for " + this.address + ":" + this.port);
		}catch(IOException iex){
			System.out.println("Failed to initialize socket for " + this.address + ":" + this.port);
			System.exit(-1);
		}
		
		try{
			this.oos = new ObjectOutputStream(this.socket.getOutputStream());
		}catch(IOException iex){
			System.out.println("Failed to get output stream.");
			System.exit(-1);
		}
		
		try{
			this.ois = new ObjectInputStream(this.socket.getInputStream());
		}catch(IOException iex){
			System.out.println("Failed to get output stream.");
			System.exit(-1);
		}
	}
	
	public boolean sendObject(Object obj){
		try{
			System.out.println("Sending obj: " + obj);
			this.oos.writeObject(obj);
			this.oos.flush();
			return true;
		}catch(Exception e){
			e.printStackTrace();
			System.out.println("Failed to send object: " + obj);
			return false;
		}
	}
	
	public Object readObject(){
		try {
			return ois.readObject();
		} catch (Exception e) {e.printStackTrace();}
		return null;
	}
	
	/**
	 * Sends a text message to the server the client is connected to.
	 * @param msg The message to send
	 * @param toUpperCase Should the case be uppercase (true) or lowercase (false)
	 * @return True if the message was sent, false if not.
	 */
	public boolean sendMessage(String msg, boolean toUpperCase){
		try{
			System.out.println("Writing message: " + msg);
			this.oos.writeObject((toUpperCase ? 1 : 0));
			this.oos.writeObject(Encrypt.EncryptString(msg, SymmetricCrypt.password));
			this.oos.flush();
			return true;
		}catch(Exception ex){
			System.out.println("Failed to send message: " + msg);
			return false;
		}
	}
	
	/**
	 * Read a text message sent by the server.
	 * @return The message read.
	 */
	public String readMessage(){
		try {
			Object obj = ois.readObject();
			if(obj instanceof byte[]){
				byte[] read =(byte[])obj;
				return new String(Decrypt.DecryptString(read, SymmetricCrypt.password));
			}
		} catch (Exception e) {e.printStackTrace();}
		return "";
	}
	
	/**
	 * Close the connection to the server.
	 * @return True if the close was successful, false if not.
	 */
	public boolean close(){
		try{
			socket.close();
			return true;
		}catch(IOException iex){
			System.out.println("Failed to close socket.");
			return false;
		}
	}
	
	public static void main(String[] args) throws Exception {
		String address = "localhost";
		int portS = 34334;
		int portB = 34443;
		int nonce = 777;
		
		// Connect to S and B
		A cs = new A(address, portS);
		A cb = new A(address, portB);
		
		// Send request to S
		NSObj ns = new NSObj();
		ns.type = NSObj.Type.INIT;
		ns.msg = "A".getBytes();
		ns.msg = S.concatBytes(ns.msg, "B".getBytes(), NSObj.getSplitter(ns.type));
		ns.msg = S.concatBytes(ns.msg, "777".getBytes(), NSObj.getSplitter(ns.type));
		cs.sendObject(ns);
		
		// Read request from S
		NSObj nsRead = (NSObj)cs.readObject();
		byte[][] read = S.splitBytes(Decrypt.DecryptString(nsRead.msg, key), NSObj.getSplitter(nsRead.type));
		if(!(new String(read[0])).equals("" + nonce)){ // Verify nonce
			System.out.println("Nonce did not match, someone tried to trick us! Exiting..");
			System.exit(-1);
		}
		if(!(new String(read[1])).equals("B")){ // Verify receiver
			System.out.println("Receiver did not match, someone tried to trick us! Exiting..");
			System.exit(-1);
		}
		cs.close();
		
		String sharedKey = new String(read[2]);
		System.out.println("Shared key is: " + sharedKey);
		
		// Send request to B
		ns = new NSObj();
		ns.type = NSObj.Type.HELLO;
		ns.msg = read[3];
		cb.sendObject(ns);
		
		// Read request from B
		nsRead = (NSObj)cb.readObject();
		read = S.splitBytes(nsRead.msg, NSObj.getSplitter(nsRead.type));
		int bNonce = Integer.valueOf(new String(Decrypt.DecryptString(read[0], sharedKey)));
		int newBNonce = bNonce - 1;
		System.out.println("Read nonce: " + bNonce);
		System.out.println("Replying with nonce: " + newBNonce);
		
		// Verify with B
		ns = new NSObj();
		ns.type = NSObj.Type.VERIFY;
		ns.msg = Encrypt.EncryptString(("" + newBNonce).getBytes(), sharedKey);
		cb.sendObject(ns);
		
		// Get a response from B
		nsRead = (NSObj)cb.readObject();
		String readMsg = new String(Decrypt.DecryptString(nsRead.msg, sharedKey));
		System.out.println("B replied: " + readMsg);
	}
}
